# Python 趣味编程：第 6 章
#  练习问题 6.3 Block类的实现
# --------------------------
# 程序名: ex06-block-3.py

from tkinter import *
from dataclasses import dataclass, field
import time

# 常量定义
BOX_LEFT = 100      # 游戏区域的左端
BOX_TOP  = 100      # 游戏区域的上端
BOX_WIDTH  = 300    # 游戏区域的宽度
BOX_HEIGHT = 300    # 游戏区域的高度

BALL_INITIAL_X = BOX_LEFT + 100  # 球最初的X位置
BALL_INITIAL_Y = BOX_TOP + 20    # 球最初的Y位置
BALL_DIAMETER = 10               # 球的直径
BALL_SPEED = 10                  # 球的速度

DURATION = 0.05      # 动画的速度

PADDLE_WIDTH = 10     # 推杆的宽度
PADDLE_HEIGHT = 50    # 推杆的高度
PADDLE_SPEED = 2      # 推杆的速度
PADDLE_START_POS = 20 # 推杆的最初位置

BLOCK_WIDTH = 10   # 砖块的宽度
BLOCK_HEIGHT = 40  # 砖块的高度
BLOCK_GAP = 20     # 砖块的间隔
BLOCK_LEFT = 5     # 最初的砖块的位置(左)
BLOCK_TOP = 5      # 最初的砖块的位置(上)

BLOCK_ROWS = 2   # 砖块的行数
BLOCK_COLS = 3   # 砖块的列数

CANVAS_WIDTH = BOX_LEFT + BOX_WIDTH + 100   # 画布的大小
CANVAS_HEIGHT = BOX_TOP + BOX_HEIGHT + 100

@dataclass
class Ball:
    id: int
    x: int
    y: int
    d: int
    vx: int

    def move(self):   # 移动球
        self.x += self.vx

    def redraw(self):     # 球的重新绘制
        canvas.coords(self.id, self.x, self.y,
                      self.x + self.d, self.y + self.d)

@dataclass
class Paddle:
    id: int
    x: int
    y: int
    w: int
    h: int
    dy: int = field(init=False, default=0)

    def move(self):       # 转动车轴
        self.y += self.dy

    def redraw(self):     # 推杆的重绘
        canvas.coords(self.id, self.x, self.y,
                      self.x + self.w, self.y + self.h)

    def go_up(self):      # 向上移动
        self.dy = -PADDLE_SPEED

    def go_down(self):    # 向下移动
        self.dy = PADDLE_SPEED

    def stop(self):       # 把车轱辘停下
        self.dy = 0

@dataclass
class Block:
    id: int
    x: int
    y: int
    w: int
    h: int

@dataclass
class Box:
    id: int
    west: int
    north: int
    east: int
    south: int
    balls: list
    blocks: list
    duration: float
    paddle: Paddle

    def __init__(self, x, y, w, h, duration):  # 游戏领域的构造函数
        self.west, self.north = (x, y)
        self.east, self.south = (x + w, y + h)
        self.balls = []
        self.blocks = []
        self.duration = duration
        self.paddle = None
        self.id = canvas.create_rectangle(x, y, x + w, y + h, fill="white")

    def create_ball(self, x, y, d, vx):  # 生成球，初始绘制
        id = canvas.create_oval(x, y, x + d, y + d, fill="black")
        return Ball(id, x, y, d, vx)

    def set_balls(self, n):     # 生成球，放入列表
        for x in range(n):
            ball = self.create_ball(BALL_INITIAL_X,
                                    BALL_INITIAL_Y + 20*x + BALL_DIAMETER,
                                    BALL_DIAMETER, BALL_SPEED )
            self.balls.append(ball)

    def create_paddle(self, x, y, w, h): # 初始显示推杆，然后返回
        id = canvas.create_rectangle(x, y, x + w, y + h, fill="blue")
        return Paddle(id, x, y, w, h)

    def set_paddle(self):   # 生成推杆并保存属性值
        self.paddle = self.create_paddle(
            self.east - PADDLE_WIDTH,
            self.north + PADDLE_START_POS,
            PADDLE_WIDTH, PADDLE_HEIGHT
            )

    def create_block(self, x, y, w, h):   # 初始显示转块，放回去
        id = canvas.create_rectangle(x, y, x + w, y + h,
                                     fill="red", outline="red")
        return Block(id, x, y, w, h)

    def set_blocks(self, rows, cols):    # 生成序列并保持属性值
        for row in range(rows):
            for col in range(cols):
                block = self.create_block(
                    self.west + BLOCK_LEFT + col * (BLOCK_WIDTH + BLOCK_GAP),
                    self.north + BLOCK_TOP + row * (BLOCK_HEIGHT + BLOCK_GAP),
                    BLOCK_WIDTH, BLOCK_HEIGHT
                    )
                self.blocks.append(block)

    def check_wall(self, ball):  # 球在墙壁上的反射
        if ball.x <= self.west or ball.x + ball.d >= self.east:
            ball.vx = - ball.vx

    def animate(self):
        for x in range(100): #iterate 50 times
            for ball in self.balls:
                ball.move()
                self.check_wall(ball)
                ball.redraw()
            self.paddle.move()
            self.paddle.redraw()
            time.sleep(self.duration)
            tk.update()

    def up_paddle(self, event):
        self.paddle.go_up()

    def down_paddle(self, event):
        self.paddle.go_down()

    def stop_paddle(self, event):
        self.paddle.stop()

#main
tk = Tk()
canvas = Canvas(tk, width=CANVAS_WIDTH,
                height=CANVAS_HEIGHT, bd=0)
canvas.pack()

box = Box(BOX_LEFT, BOX_TOP, BOX_WIDTH, BOX_HEIGHT, duration=DURATION)

canvas.bind_all("<KeyPress-Up>", box.up_paddle)
canvas.bind_all("<KeyRelease-Up>", box.stop_paddle)
canvas.bind_all("<KeyPress-Down>", box.down_paddle)
canvas.bind_all("<KeyRelease-Down>", box.stop_paddle)

box.set_balls(1)
box.set_paddle()
box.set_blocks(BLOCK_ROWS, BLOCK_COLS)
box.animate()
